#include "mainwindow.h"
#include "collectionimage.h"
#include "collectiontab.h"
#include "dib.h"
#include "imagetab.h"
#include "histogramdialog.h"
#include "metadatadialog.h"
#include "resource.h"
#include "util.h"
#include "operations.h"

#include "dot_operations.h"
#include "rebuilding_operations.h"
#include "sizing_operations.h"
#include "file_operations.h"

#include <commctrl.h>
#include <fstream>

//Initialize the static data members.
MainWindow *MainWindow::_instance = NULL;
ATOM MainWindow::_wndClass = NULL;

MainWindow::MainWindow()
{
	_hWnd = NULL;
	_hWndBtnMetaData = NULL;
	_tabCtrl = NULL;
	_collectionBox = NULL;
	_imageBox = NULL;

	//List all filters for each type (the id's are defined in resource.h).
	WORD dotOperationList[] = {
		ID_COLORMODE_GRAYSCALE,
		ID_COLORMODE_MONOTONE,
		ID_COLORMODE_YCBCR,
		ID_IMAGE_RGBLEVELS,
		ID_IMAGE_CONTRAST,
		ID_IMAGE_INVERT,
		ID_IMAGE_TRESHOLD,
		ID_MATHMATICAL_EXPONENT,
		ID_MATHMATICAL_LOGARITHM,
		ID_MATHMATICAL_NATURALLOGARITHM
		};
	WORD rebuildingOperationList[] = {
		ID_FLIP_HORIZONTAL,
		ID_FLIP_VERTICAL,
		ID_FILTER_MEDIAN,
		ID_FILTER_AVERAGE,
		ID_FILTER_EMBOSS,
		ID_FILTER_GAUSS,
		ID_SOBEL_HORIZONTAL,
		ID_SOBEL_VERTICAL,
		ID_FILTER_LAPLACE,
		ID_MATHMATICAL_DISCRETECOSINETRANSFORM
		};
	WORD sizingOperationList[] = {
		ID_IMAGE_ROTATE,
		ID_IMAGE_RESIZE
		};
	WORD fileOperationList[] = {
		ID_MATHMATICAL_ADD,
		ID_MATHMATICAL_MULTIPLY,
		ID_MATHMATICAL_SUBTRACT
		};

	//Fill the _menuFunc map with menu identifiers and functions to call when the menu
	//item is clicked.
	std::pair<WORD,MenuFunc> openFunc( ID_FILE_OPEN40002, &MainWindow::openFile );
	_menuFunc.insert( openFunc );
	std::pair<WORD,MenuFunc> saveFunc( ID_FILE_SAVE40004, &MainWindow::saveFile );
	_menuFunc.insert( saveFunc );
	std::pair<WORD,MenuFunc> saveAsFunc( ID_FILE_SAVEAS, &MainWindow::saveFileAs );
	_menuFunc.insert( saveAsFunc );
	std::pair<WORD,MenuFunc> closeFunc( ID_FILE_CLOSE40003, &MainWindow::closeFile );
	_menuFunc.insert( closeFunc );
	std::pair<WORD,MenuFunc> exitFunc( ID_FILE_EXIT, &MainWindow::exitApplication );
	_menuFunc.insert( exitFunc );
	std::pair<WORD,MenuFunc> newCollectionFunc( ID_FILE_NEWCOLLECTION, &MainWindow::newCollection );
	_menuFunc.insert( newCollectionFunc );
	std::pair<WORD,MenuFunc> addImageFunc( ID_COLLECTION_ADDIMAGE, &MainWindow::addImage );
	_menuFunc.insert( addImageFunc );
	std::pair<WORD,MenuFunc> searchFunc( ID_COLLECTION_SEARCH, &MainWindow::search );
	_menuFunc.insert( searchFunc );
	std::pair<WORD,MenuFunc> exportFunc( ID_FILE_EXPORTCOLLECTION, &MainWindow::exportCollection );
	_menuFunc.insert( exportFunc );
	std::pair<WORD,MenuFunc> histogramFunc( ID_IMAGE_HISTOGRAM, &MainWindow::histogram );
	_menuFunc.insert( histogramFunc );

	//Add all dot operations to the _menuFunc map.
	for( unsigned int i = 0; i < 10; ++i )
	{
		std::pair<WORD,MenuFunc> func( dotOperationList[ i ], &MainWindow::dotOperation );
		_menuFunc.insert( func );
	}
	//Add all rebuilding operations to the _menuFunc map.
	for( unsigned int i = 0; i < 10; ++i )
	{
		std::pair<WORD,MenuFunc> func( rebuildingOperationList[ i ], &MainWindow::rebuildingOperation );
		_menuFunc.insert( func );
	}
	//Add all sizing operations to the _menuFunc map.
	for( unsigned int i = 0; i < 2; ++i )
	{
		std::pair<WORD,MenuFunc> func( sizingOperationList[ i ], &MainWindow::sizingOperation );
		_menuFunc.insert( func );
	}
	//Add all file operations to the _menuFunc map.
	for( unsigned int i = 0; i < 3; ++i )
	{
		std::pair<WORD,MenuFunc> func( fileOperationList[ i ], &MainWindow::fileOperation );
		_menuFunc.insert( func );
	}
}

MainWindow::~MainWindow()
{
	if( IsWindow( _hWnd ) )
	{
		//Detach the instance from this window and destroy the window.
		SetWindowLongPtr( _hWnd, GWLP_USERDATA, NULL );
		DestroyWindow( _hWnd );
	}
	_instance = NULL;
}

MainWindow* MainWindow::instance()
{
	if( _instance == NULL )
	{
		//Create the singleton instance.
		_instance = new MainWindow();
	}
	return _instance;
}

bool MainWindow::create( HWND hWndParent, HINSTANCE hInstance )
{
	//Register the "MainWindow" window class.
	if( registerWndClass( hInstance ) == false )
	{
		return false;
	}

	// Load the window classes for a status bar and a tabcontrol.
	INITCOMMONCONTROLSEX initCtrls;
	initCtrls.dwSize = sizeof( INITCOMMONCONTROLSEX );
#if _WIN32_WINNT >= 0x501 //Windows 2000 or higher?
	initCtrls.dwICC = ICC_BAR_CLASSES | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES;
#else
	initCtrls.dwICC = ICC_BAR_CLASSES | ICC_TAB_CLASSES;
#endif
	InitCommonControlsEx( &initCtrls );

	_hWnd = CreateWindowEx(
		0, //dwExStyle
		(LPCWSTR) _wndClass, //lpClassName
		L"TI6 Collection Editor", //lpWindowName
		WS_OVERLAPPEDWINDOW | WS_VISIBLE, //dwStyle
		CW_USEDEFAULT, //X
		CW_USEDEFAULT, //Y
		700, //nWidth
		500, //nHeight
		hWndParent, //hWndParent
		NULL, //hMenu
		hInstance, //hInstance
		NULL //lpParam
		);
	if( _hWnd == NULL )
	{
		//Could not create the window.
		return false;
	}

	// Create a status bar.
	_hWndStatusBar = CreateWindow(
		STATUSCLASSNAME,
		NULL,
		WS_CHILD,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		_hWnd,
		NULL,
		hInstance,
		NULL
		);
	if( _hWndStatusBar == NULL )
	{
		return false;
	}

	//Create the metadata button (this button will be made visible when
	//a collection tab is open.
	_hWndBtnMetaData = CreateWindow(
		L"BUTTON", //lpClassName
		L"Metadata", //lpWindowName
		WS_CHILD, //wsStyle
		0, //x
		0, //y
		100, //nWidth
		25, //nHeight
		_hWnd, //hWndParent
		NULL, //hMenu
		hInstance, //hInstance
		NULL //lpParam
		);
	if( _hWndBtnMetaData == NULL )
	{
		return false;
	}

	//Divide the status bar in two parts.
	INT aWidths[] = { 100, -1 };
	SendMessage(
		_hWndStatusBar,
		SB_SETPARTS,
		(WPARAM) 2,
		(LPARAM) aWidths
		);
	SendMessage(
		_hWndStatusBar,
		SB_SETTEXT,
		(WPARAM) SBT_OWNERDRAW,
		(LPARAM) NULL
		);
	SendMessage(
		_hWndStatusBar,
		SB_SETTEXT,
		(WPARAM) 1,
		(LPARAM) L"Ready"
		);
	ShowWindow( _hWndStatusBar, SW_SHOW );

	//Create the tab control.
	_tabCtrl = new TabControl();
	_tabCtrl->create( _hWnd, hInstance );

	//Create the collectionbox as a child of the tab control. This component is used to show an collection.
	_collectionBox = new CollectionBox();
	_collectionBox->create( _tabCtrl->hWnd(), hInstance );

	//Create the imagebox as a child of the tab control. This component is used to show an image.
	_imageBox = new ImageBox();
	_imageBox->create( _tabCtrl->hWnd(), hInstance );

	SetWindowLongPtr( _hWnd, GWLP_USERDATA, (LONG_PTR) this );

	layout( NULL ); //Reposition all components on the window.
	UpdateWindow( _hWnd );
	return true;
}

bool MainWindow::show( int nCmdShow )
{
	return ShowWindow( _hWnd, nCmdShow ) != FALSE;
}

LRESULT MainWindow::handleMessage( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	switch( uMsg )
	{
	case WM_CLOSE:
		DestroyWindow( _hWnd );
		break;
	case WM_COMMAND:
		{
			if( HIWORD( wParam ) == 0 )
			{
				if( lParam == 0 )
				{
					//This is a menu message.
					WORD id = LOWORD( wParam );
					std::map<WORD,MenuFunc>::iterator it = _menuFunc.find( id );
					if( it != _menuFunc.end() )
					{
						//Call the function which is associated by this menu item. The
						//argument is the menu identifier (e.g. ID_FILE_SAVEAS).
						(this->*(it->second))( id );
					}
				}
				else if( lParam == (LPARAM) _hWndBtnMetaData )
				{
					//The metadata button is clicked.
					Collection *collection = _collectionBox->collection();
					if( collection )
					{
						if( collection->selected() != -1 )
						{
							//Show the metadata dialog.
							MetaDataDialog dialog;
							dialog.setParent( _hWnd );
							dialog.setCollection( collection );
							dialog.setImageNum( collection->selected() );
							dialog.show();
						}
					}
				}
			}
		}
		break;
	case WM_DESTROY:
		PostQuitMessage( 0 );
		break;
	case WM_DRAWITEM:
		{
			//A child window should be repainted. In our case, it is
			//the progress bar.
			LPDRAWITEMSTRUCT drawItemStruct = (LPDRAWITEMSTRUCT) lParam;
			LOGBRUSH logBrush;
			HBRUSH hBrush;
			RECT rect;

			rect.top = 0;
			rect.left = 0;
			rect.bottom = 20;
			rect.right = 100;

			logBrush.lbStyle = BS_SOLID;
			logBrush.lbColor = RGB( 128, 128, 128 );
			hBrush = CreateBrushIndirect( &logBrush );
			FillRect( drawItemStruct->hDC, &rect, hBrush );
		}
		break;
	case WM_NOTIFY:
		if( ( (LPNMHDR) lParam )->code == TCN_SELCHANGE )
		{
			//The user clicked another tab. Because this control is only the
			//tab bar, we are now responsible to change the rest of the
			//page/window.
			TabControl::Tab *tab = _tabCtrl->getTab( _tabCtrl->currentTab() );
			if( tab )
			{
				if( typeid( *tab ) == typeid( CollectionTab ) )
				{
					//The user switched to a collection tab.
					_collectionBox->setCollection( ( (CollectionTab*) tab )->collection() );
					_collectionBox->show();
					_imageBox->show( SW_HIDE );
					ShowWindow( _hWndBtnMetaData, SW_SHOW );
				}
				else if( typeid( *tab ) == typeid( ImageTab ) )
				{
					//The user switched to an image tab.
					_imageBox->setDib( ( (ImageTab*) tab )->dib() );
					_imageBox->center();
					_collectionBox->show( SW_HIDE );
					_imageBox->show();
					ShowWindow( _hWndBtnMetaData, SW_HIDE );
				}
			}
		}
		break;
	case WM_PAINT:
		{
			//The main window is repainted. We can draw graphics between the
			//calls to BeginPaint() and EndPaint().
			PAINTSTRUCT ps;
			HDC hDC = BeginPaint( _hWnd, &ps );
			EndPaint( _hWnd, &ps );
		}
		break;
	case WM_SIZE:
		{
			//Store the new size of the window in a SIZE struct.
			SIZE size;
			size.cx = (LONG) LOWORD( lParam );
			size.cy = (LONG) HIWORD( lParam );

			//Set the position and size of all components.
			layout( &size );
		}
		break;
	default:
		//We don't handle this message. Use the default message handling procedure.
		return DefWindowProc( _hWnd, uMsg, wParam, lParam );
	}
	return 0;
}

void MainWindow::layout( SIZE *size )
{
	SIZE s;
	if( size )
	{
		s = *size;
	}
	else
	{
		//The size is not specified. Request the size of the window.
		RECT rect;
		GetWindowRect( _hWnd, &rect );
		s.cx = rect.right - rect.left;
		s.cy = rect.bottom - rect.top;
	}

	//Move the status bar. The new position is automatically the bottom
	//of the window.
	SendMessage( _hWndStatusBar, WM_SIZE, 0, 0 );

	//Calculate the position and size for the tab control and move the control.
	RECT tabCtrlRect, statusBarRect, imageBoxRect;
	GetClientRect( _hWnd, &tabCtrlRect );
	GetWindowRect( _hWndStatusBar, &statusBarRect );
	tabCtrlRect.bottom -= ( statusBarRect.bottom - statusBarRect.top );
	_tabCtrl->setPosition( tabCtrlRect );

	//Move and resize the image box.
	GetClientRect( _tabCtrl->hWnd(), &imageBoxRect );
	imageBoxRect.right -= 5;
	imageBoxRect.top += 26;
	_imageBox->setPosition( imageBoxRect );
	imageBoxRect.bottom -= 29;
	_collectionBox->setPosition( imageBoxRect );

	//Move the metadata button.
	MoveWindow(
		_hWndBtnMetaData,
		imageBoxRect.right - 100,
		imageBoxRect.bottom,
		100,
		25,
		TRUE
		);
}

bool MainWindow::registerWndClass( HINSTANCE hInstance )
{
	WNDCLASSEX wc;

	wc.cbSize        = sizeof( WNDCLASSEX );
	wc.style         = 0; //Do not use special window styles.
	wc.lpfnWndProc   = &Window::ooDispatcher; //The message handling function.
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hInstance;
	wc.hIcon         = LoadIcon( NULL, IDI_APPLICATION );
	wc.hCursor       = LoadCursor( NULL, IDC_ARROW );

	//Use COLOR_BTNFACE as the color of the window. We must add 1 to this color.
	wc.hbrBackground = (HBRUSH) ( COLOR_BTNFACE + 1 );
	wc.lpszMenuName  = MAKEINTRESOURCE( IDR_MENU1 );
	wc.lpszClassName = L"MainWindow";
	wc.hIconSm       = LoadIcon( NULL, IDI_APPLICATION );

	_wndClass = RegisterClassEx( &wc );
	return _wndClass != NULL;
}

bool MainWindow::openImage( wchar_t *fileName )
{
	DIB sourceDib, *destDib;
	std::ifstream file;
	SIZE size;
	ImageTab *imageTab;

	//Pointer to a bitmap header. This will specify the format we use when
	//editing images (24 bits color). We will convert the image to this format.
	LPBITMAPINFO format;

	file.open( fileName, std::ios::binary );
	if( sourceDib.readBitmap( file ) == false )
	{
		return false;
	}
	destDib = new DIB();
	format = new BITMAPINFO();
	imageTab = new ImageTab();
	size = sourceDib.size();

	//Specify the format to convert the image to.
	format->bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
	format->bmiHeader.biWidth = size.cx;
	format->bmiHeader.biHeight = size.cy;
	format->bmiHeader.biPlanes = 1;
	format->bmiHeader.biBitCount = 24;
	format->bmiHeader.biCompression = BI_RGB;

	//Make sure the height is positive, so we get a bottom-up DIB.
	if( format->bmiHeader.biHeight < 0 )
	{
		format->bmiHeader.biHeight *= -1;
	}

	//Copy the image in sourceDib to destDib, which is in another format.
	if( sourceDib.convertTo( *destDib, format ) == false )
	{
		//The conversion failed.
		delete destDib;
		delete imageTab;
		return false;
	}

	//Initialize the image tab.
	imageTab->setFileName( fileName );
	imageTab->setDib( destDib );

	//Extract the file name from the path.
	wchar_t tabCaption[ 256 ];
	memset( tabCaption, 0, 256 * sizeof( wchar_t ) );
	size_t j = 0;
	for( size_t i = 0; fileName[ i ] != L'\0'; ++i )
	{
		if( fileName[ i ] == L'/' || fileName[ i ] == L'\\' )
		{
			j = 0;
			continue;
		}
		else
		{
			tabCaption[ j++ ] = fileName[ i ];
		}
	}
	tabCaption[ j ] = L'\0';

	if( _tabCtrl->addTab( tabCaption, imageTab, _tabCtrl->tabCount() ) == -1 )
	{
		delete imageTab;
		return false;
	}
	if( _tabCtrl->tabCount() == 1 )
	{
		//This is the first tab. Make the tab control visible.
		_tabCtrl->show( SW_SHOW );
		_imageBox->setDib( destDib );
		_imageBox->center();
		_imageBox->show();
	}
	return true;
}

DIB* MainWindow::openAsThumbnail( wchar_t *fileName )
{
	DIB sourceDib, *destDib;
	std::ifstream file;
	SIZE size;

	//Pointer to a bitmap header. This will specify the format we use when
	//editing images (24 bits color). We will convert the image to this format.
	LPBITMAPINFO format;

	file.open( fileName, std::ios::binary );
	if( sourceDib.readBitmap( file ) == false )
	{
		return NULL;
	}
	destDib = new DIB();
	format = new BITMAPINFO();
	size = sourceDib.size();

	//Calculate a new size to fit the image in 120 * 100 pixels.
	double factor = min( 120.0 / (double) size.cx, 100.0 / (double) size.cy );
	size.cx = (LONG) ( (double) size.cx * factor );
	size.cy = (LONG) ( (double) size.cy * factor );

	//Specify the format to convert the image to.
	format->bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
	format->bmiHeader.biWidth = size.cx;
	format->bmiHeader.biHeight = size.cy;
	format->bmiHeader.biPlanes = 1;
	format->bmiHeader.biBitCount = 24;
	format->bmiHeader.biCompression = BI_RGB;

	// Make sure the height is positive, so we get a bottom-up DIB.
	if( format->bmiHeader.biHeight < 0 )
	{
		format->bmiHeader.biHeight *= -1;
	}

	//Copy the image in sourceDib to destDib, which is in another format
	//and size.
	if( sourceDib.convertTo( *destDib, format ) == false )
	{
		delete destDib;
		return NULL;
	}
	return destDib;
}

void MainWindow::openFile( WORD )
{
	//The WORD is a dummy parameter, to make the function fit the MenuFunc type.

	//Show the open file dialog.
	wchar_t fileName[ 256 ];
	memset( fileName, 0, 256 );
	OPENFILENAME openFileName;
	openFileName.lStructSize = sizeof( OPENFILENAME );
	openFileName.hwndOwner = _hWnd;
	openFileName.lpstrFilter = L"Bitmap Image (*.bmp)\0*.BMP;*.DIB\0";
	openFileName.lpstrCustomFilter = NULL;
	openFileName.nFilterIndex = 0;
	openFileName.lpstrFile = fileName;
	openFileName.nMaxFile = 256;
	openFileName.lpstrFileTitle = NULL;
	openFileName.lpstrInitialDir = NULL;
	openFileName.lpstrTitle = NULL;
	openFileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
	openFileName.nFileOffset = 0;
	openFileName.nFileExtension = 0;
	openFileName.lpstrDefExt = NULL;
	openFileName.lCustData = NULL;
	openFileName.lpTemplateName = NULL;
#if _WIN32_WINNT >= 0x0500
	openFileName.pvReserved = NULL;
	openFileName.dwReserved = 0;
#endif //_WIN32_WINNT >= 0x0500
	openFileName.FlagsEx = 0;
	if( GetOpenFileName( &openFileName ) )
	{
		if( openImage( fileName ) == false )
		{
			MessageBox( NULL, L"The image could not be opened.", L"Message",
				MB_ICONEXCLAMATION | MB_OK );
		}
	}
}

void MainWindow::saveFile( WORD )
{
	TabControl::Tab *tab = _tabCtrl->getTab( _tabCtrl->currentTab() );
	if( tab == NULL )
	{
		return; //There is no active tab.
	}
	if( typeid( *tab ) == typeid( ImageTab ) )
	{
		//We need to save an image.
		ImageTab *imageTab = (ImageTab*) tab;
		if( imageTab->hasUnsavedChanges() )
		{
			//The image contains unsaved changes, so save the file.
			wchar_t *fileName = imageTab->fileName();
			if( fileName == NULL )
			{
				//This is the first time the image is saved.
				saveFileAs();
				return;
			}

			//Truncate and open the existing image file.
			std::ofstream ostr( fileName, std::ios::binary | std::ios::trunc );
			if( ostr.is_open() == false )
			{
				MessageBox( NULL, L"The file could not be opened.", L"Message",
					MB_ICONEXCLAMATION | MB_OK );
				return;
			}

			if( imageTab->dib()->saveBitmap( ostr ) == false )
			{
				MessageBox( NULL, L"An error occured while saving the file.", L"Message",
					MB_ICONEXCLAMATION | MB_OK );
			}
		}
	}
}

void MainWindow::saveFileAs( WORD )
{
	TabControl::Tab *tab = _tabCtrl->getTab( _tabCtrl->currentTab() );
	if( tab == NULL )
	{
		return; //There is no active tab.
	}
	if( typeid( *tab ) == typeid( ImageTab ) )
	{
		//We need to save an image.
		ImageTab *imageTab = (ImageTab*) tab;

		//Get the save file dialog.
		wchar_t fileName[ 256 ];
		memset( fileName, 0, 256 );
		OPENFILENAME openFileName;
		openFileName.lStructSize = sizeof( OPENFILENAME );
		openFileName.hwndOwner = _hWnd;
		openFileName.lpstrFilter = L"Bitmap Image (*.bmp)\0*.BMP;*.DIB\0";
		openFileName.lpstrCustomFilter = NULL;
		openFileName.nFilterIndex = 0;
		openFileName.lpstrFile = fileName;
		openFileName.nMaxFile = 256;
		openFileName.lpstrFileTitle = NULL;
		openFileName.lpstrInitialDir = NULL;
		openFileName.lpstrTitle = NULL;
		openFileName.Flags = 0;
		openFileName.nFileOffset = 0;
		openFileName.nFileExtension = 0;
		openFileName.lpstrDefExt = NULL;
		openFileName.lCustData = NULL;
		openFileName.lpTemplateName = NULL;
	#if _WIN32_WINNT >= 0x0500
		openFileName.pvReserved = NULL;
		openFileName.dwReserved = 0;
	#endif //_WIN32_WINNT >= 0x0500
		openFileName.FlagsEx = 0;
		if( GetSaveFileName( &openFileName ) )
		{
			std::ofstream ostr( fileName, std::ios::binary | std::ios::trunc );
			if( ostr.is_open() == false )
			{
				MessageBox( NULL, L"The file could not be opened or created.", L"Message",
					MB_ICONEXCLAMATION | MB_OK );
				return;
			}
			
			if( imageTab->dib()->saveBitmap( ostr ) == false )
			{
				MessageBox( NULL, L"An error occured while saving the file.", L"Message",
					MB_ICONEXCLAMATION | MB_OK );
			}
		}
	}
}

void MainWindow::closeFile( WORD )
{
	TabControl::Tab *tab = _tabCtrl->getTab( _tabCtrl->currentTab() );
	if( tab == NULL )
	{
		return; //There is no active tab.
	}
	if( typeid( *tab ) == typeid( ImageTab ) )
	{
		//We need to close an image tab.
		ImageTab *imageTab = (ImageTab*) tab;
		if( imageTab->hasUnsavedChanges() )
		{
			//Ask the user if changes needs to be saved.
			if( MessageBox( NULL, L"The file has unsaved changes. Do you want to save the file?", L"Message",
				MB_ICONQUESTION | MB_YESNO ) == IDYES )
			{
				saveFile();
			}
		}
		_imageBox->show( SW_HIDE );
		_imageBox->setDib( NULL );
		delete imageTab->dib();
	}
	else if( typeid( *tab ) == typeid( CollectionTab ) )
	{
		//We need to close a collection tab.
		CollectionTab *collectionTab = (CollectionTab*) tab;

		_collectionBox->show( SW_HIDE );
		ShowWindow( _hWndBtnMetaData, SW_HIDE );
	}

	_tabCtrl->closeTab( _tabCtrl->currentTab() );
	if( _tabCtrl->tabCount() == 0 )
	{
		//The last tab is closed.
		_tabCtrl->show( SW_HIDE );
	}
	delete tab;
}

void MainWindow::exitApplication( WORD )
{
	//Close all tabs using closeFile(). The user receives a message if there are any
	//unsaved changes.
	unsigned int tabCount = _tabCtrl->tabCount();
	for( unsigned int i = 0; i < tabCount; ++i )
	{
		closeFile();
	}
	PostQuitMessage( 0 );
}

void MainWindow::newCollection( WORD )
{
	Collection *collection = new Collection();
	CollectionTab *collectionTab = new CollectionTab();
	collectionTab->setCollection( collection );
	if( _tabCtrl->addTab( L"Collection", collectionTab, _tabCtrl->tabCount() ) == -1 )
	{
		delete collectionTab;
		return;
	}
	if( _tabCtrl->tabCount() == 1 )
	{
		//This is the first tab. Show the tab control and the components
		//related to a collection tab page (collection box and metadata
		//button).
		_collectionBox->show();
		_collectionBox->setCollection( collection );
		_tabCtrl->show();
		ShowWindow( _hWndBtnMetaData, SW_SHOW );
	}
}

void MainWindow::addImage( WORD )
{
	//Add an image to the collection (which should be open in the current tab).

	TabControl::Tab *tab = _tabCtrl->getTab( _tabCtrl->currentTab() );
	if( typeid( *tab ) == typeid( CollectionTab ) )
	{
		//The active document is a collection. Show the open file dialog
		//to let the user select the image to add.
		wchar_t fileName[ 256 ];
		memset( fileName, 0, 256 );
		OPENFILENAME openFileName;
		openFileName.lStructSize = sizeof( OPENFILENAME );
		openFileName.hwndOwner = _hWnd;
		openFileName.lpstrFilter = L"Bitmap Image (*.bmp)\0*.BMP;*.DIB\0";
		openFileName.lpstrCustomFilter = NULL;
		openFileName.nFilterIndex = 0;
		openFileName.lpstrFile = fileName;
		openFileName.nMaxFile = 256;
		openFileName.lpstrFileTitle = NULL;
		openFileName.lpstrInitialDir = NULL;
		openFileName.lpstrTitle = NULL;
		openFileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
		openFileName.nFileOffset = 0;
		openFileName.nFileExtension = 0;
		openFileName.lpstrDefExt = NULL;
		openFileName.lCustData = NULL;
		openFileName.lpTemplateName = NULL;
	#if _WIN32_WINNT >= 0x0500
		openFileName.pvReserved = NULL;
		openFileName.dwReserved = 0;
	#endif //_WIN32_WINNT >= 0x0500
		openFileName.FlagsEx = 0;
		if( GetOpenFileName( &openFileName ) )
		{
			//Read the image and store a thumbnail in memory.
			DIB *dib = openAsThumbnail( fileName );
			if( dib != NULL )
			{
				Collection *collection = ( (CollectionTab*) tab )->collection();

				//Add the image to the collection.
				CollectionImage img;
				img.setFileName( fileName );
				img.setThumbnail( dib ); //the DIB will be deleted automatically.
				collection->addImage( img );
				InvalidateRect( _collectionBox->hWnd(), NULL, TRUE );
			}
			else
			{
				MessageBox( NULL, L"The image could not be opened.", L"Message",
					MB_ICONEXCLAMATION | MB_OK );
			}
		}
	}
}

void MainWindow::search( WORD )
{
	TabControl::Tab *tab = _tabCtrl->getTab( _tabCtrl->currentTab() );
	if( tab )
	{
		if( typeid( *tab ) == typeid( CollectionTab ) )
		{
			//The active tab is a collection tab.
			CollectionTab *collectionTab = (CollectionTab*) tab;
			Collection *collection = collectionTab->collection();
			if( collection )
			{
				collection->search();
				if( collection->selected() >= (int) collection->size() )
				{
					collection->setSelected( -1 ); //Clear selection
				}
				InvalidateRect( _collectionBox->hWnd(), NULL, TRUE );
			}
		}
	}
}

void MainWindow::exportCollection( WORD )
{
	TabControl::Tab *tab = _tabCtrl->getTab( _tabCtrl->currentTab() );
	if( tab )
	{
		if( typeid( *tab ) == typeid( CollectionTab ) )
		{
			CollectionTab *collectionTab = (CollectionTab*) tab;
			Collection *collection = collectionTab->collection();
			if( collection )
			{
				//Get the save file dialog.
				wchar_t fileName[ 256 ];
				memset( fileName, 0, 256 );
				OPENFILENAME openFileName;
				openFileName.lStructSize = sizeof( OPENFILENAME );
				openFileName.hwndOwner = _hWnd;
				openFileName.lpstrFilter = L"Collection (*.xml)\0*.XML\0";
				openFileName.lpstrCustomFilter = NULL;
				openFileName.nFilterIndex = 0;
				openFileName.lpstrFile = fileName;
				openFileName.nMaxFile = 256;
				openFileName.lpstrFileTitle = NULL;
				openFileName.lpstrInitialDir = NULL;
				openFileName.lpstrTitle = NULL;
				openFileName.Flags = 0;
				openFileName.nFileOffset = 0;
				openFileName.nFileExtension = 0;
				openFileName.lpstrDefExt = NULL;
				openFileName.lCustData = NULL;
				openFileName.lpTemplateName = NULL;
#if _WIN32_WINNT >= 0x0500
				openFileName.pvReserved = NULL;
				openFileName.dwReserved = 0;
#endif //_WIN32_WINNT >= 0x0500
				openFileName.FlagsEx = 0;
				if( GetSaveFileName( &openFileName ) )
				{
					//Convert the filename from unicode to ansi ascii.
					char *ansi = util::wcsToAnsi( fileName );
					if( ansi )
					{
						if( collection->save( ansi ) )
							return;
					}
					MessageBox( NULL, L"An error occured while exporting the collection.", L"Message",
						MB_ICONEXCLAMATION | MB_OK );
				}
			}
		}
	}
}

void MainWindow::histogram( WORD )
{
	if( &*_imageBox->dib() == NULL )
	{
		//The current document is not an image.
		return;
	}

	//Set the DIB to use and show the dialog.
	HistogramDialog dialog;
	dialog.setParent( _hWnd );
	dialog.setDIB( &*_imageBox->dib() );
	dialog.show();
}

void MainWindow::dotOperation( WORD id )
{
	//Operations which are performed directly on an image. No data is copied.

	if( &*_imageBox->dib() == NULL )
	{
		//There is no image to apply the operation on.
		return;
	}

	//Create the task.
	Task *task = new Task();
	task->_originalImage = _imageBox->dib();
	task->_destinationImage = NULL;
	task->_operationType = Task::DOT_OPERATION;
	task->_data = NULL;

	//Call a function for operation-specific setup.
	bool setupComplete = false;
	switch( id )
	{
	case ID_COLORMODE_GRAYSCALE:
		setupComplete = grayscaleSetup( task );
		break;
	case ID_COLORMODE_MONOTONE:
		setupComplete = monotoneSetup( task );
		break;
	case ID_COLORMODE_YCBCR:
		setupComplete = yCbCrSetup( task );
		break;
	case ID_IMAGE_RGBLEVELS:
		setupComplete = rgbLevelsSetup( task );
		break;
	case ID_IMAGE_CONTRAST:
		setupComplete = contrastSetup( task );
		break;
	case ID_IMAGE_INVERT:
		setupComplete = invertSetup( task );
		break;
	case ID_IMAGE_TRESHOLD:
		setupComplete = tresholdSetup( task );
		break;
	case ID_MATHMATICAL_EXPONENT:
		setupComplete = exponentSetup( task );
		break;
	case ID_MATHMATICAL_LOGARITHM:
		setupComplete = logarithmSetup( task );
		break;
	case ID_MATHMATICAL_NATURALLOGARITHM:
		setupComplete = naturalLogarithmSetup( task );
		break;
	default:
		break;
	}
	if( setupComplete == false )
	{
		delete task;
		return;
	}

	//The setup is complete. Start the filter operation, which will compute all new
	//pixel values.
	if( performOperation( task, id ) )
	{
		taskCompleted( task );
	}
	else
	{
		taskFailed( task );
	}
}

void MainWindow::rebuildingOperation( WORD id )
{
	//Operations which build a new image based on the existing image. The size
	//of the image does not change.

	if( &*_imageBox->dib() == NULL )
	{
		//There is no image to apply the operation on.
		return;
	}

	//Create the task.
	Task *task = new Task();
	task->_originalImage = _imageBox->dib();
	task->_destinationImage = new DIB();
	task->_operationType = Task::REBUILDING_OPERATION;
	task->_data = NULL;

	//Initialize the destination image as an empty (white) image of the same
	//size as the original image.
	task->_destinationImage->setFormat( task->_originalImage->format() );
	task->_destinationImage->create();

	//Call a function for operation-specific setup.
	bool setupComplete = false;
	switch( id )
	{
	case ID_FLIP_HORIZONTAL:
		setupComplete = flipHorizontalSetup( task );
		break;
	case ID_FLIP_VERTICAL:
		setupComplete = flipVerticalSetup( task );
		break;
	case ID_FILTER_MEDIAN:
		setupComplete = medianSetup( task );
		break;
	case ID_FILTER_AVERAGE:
		setupComplete = averageSetup( task );
		break;
	case ID_FILTER_EMBOSS:
		setupComplete = embossSetup( task );
		break;
	case ID_FILTER_GAUSS:
		setupComplete = gaussSetup( task );
		break;
	case ID_SOBEL_HORIZONTAL:
		setupComplete = sobelHorizontalSetup( task );
		break;
	case ID_SOBEL_VERTICAL:
		setupComplete = sobelVerticalSetup( task );
		break;
	case ID_FILTER_LAPLACE:
		setupComplete = laplaceSetup( task );
		break;
	case ID_MATHMATICAL_DISCRETECOSINETRANSFORM:
		setupComplete = discreteCosineTransformSetup( task );
		break;
	default:
		break;
	}
	if( setupComplete == false )
	{
		delete task;
		return;
	}

	//The setup is complete. Start the filter operation, which will compute all new
	//pixel values.
	if( performOperation( task, id ) )
	{
		taskCompleted( task );
	}
	else
	{
		taskFailed( task );
	}
}

void MainWindow::sizingOperation( WORD id )
{
	//Operations which build a new image based on the existing image. The size
	//of the image may change.

	if( &*_imageBox->dib() == NULL )
	{
		//There is no image to apply the operation on.
		return;
	}

	//Create the task.
	Task *task = new Task();
	task->_originalImage = _imageBox->dib();
	task->_destinationImage = NULL;
	task->_operationType = Task::SIZING_OPERATION;
	task->_data = NULL;

	//Call a function for operation-specific setup.
	bool setupComplete = false;
	switch( id )
	{
	case ID_IMAGE_ROTATE:
		setupComplete = rotateSetup( task );
		break;
	case ID_IMAGE_RESIZE:
		setupComplete = resizeSetup( task );
		break;
	default:
		break;
	}
	if( setupComplete == false )
	{
		delete task;
		return;
	}

	//If this assertion fails, the setup function did not create a destination image
	//for the filter (a pointer to this image should be in task->_destinationImage ).
	assert( task->_destinationImage );

	//The setup is complete. Start the filter operation, which will compute all new
	//pixel values.
	if( performOperation( task, id ) )
	{
		taskCompleted( task );
		_imageBox->center();
	}
	else
	{
		taskFailed( task );
	}
}

void MainWindow::fileOperation( WORD id )
{
	//Operations which requires opening a second file. The existing image is
	//changed in accordance with the second file (these are a sort of merge
	//operations).

	if( &*_imageBox->dib() == NULL )
	{
		//There is no image to apply the operation on.
		return;
	}

	//Create the task.
	Task *task = new Task();
	task->_originalImage = NULL;
	task->_destinationImage = _imageBox->dib();
	task->_operationType = Task::FILE_OPERATION;
	task->_data = NULL;

	//Show the open file dialog.
	wchar_t fileName[ 256 ];
	memset( fileName, 0, 256 );
	OPENFILENAME openFileName;
	openFileName.lStructSize = sizeof( OPENFILENAME );
	openFileName.hwndOwner = _hWnd;
	openFileName.lpstrFilter = L"Bitmap Image (*.bmp)\0*.BMP;*.DIB\0";
	openFileName.lpstrCustomFilter = NULL;
	openFileName.nFilterIndex = 0;
	openFileName.lpstrFile = fileName;
	openFileName.nMaxFile = 256;
	openFileName.lpstrFileTitle = NULL;
	openFileName.lpstrInitialDir = NULL;
	openFileName.lpstrTitle = NULL;
	openFileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
	openFileName.nFileOffset = 0;
	openFileName.nFileExtension = 0;
	openFileName.lpstrDefExt = NULL;
	openFileName.lCustData = NULL;
	openFileName.lpTemplateName = NULL;
#if _WIN32_WINNT >= 0x0500
	openFileName.pvReserved = NULL;
	openFileName.dwReserved = 0;
#endif //_WIN32_WINNT >= 0x0500
	openFileName.FlagsEx = 0;
	if( GetOpenFileName( &openFileName ) == FALSE )
	{
		return; //The user cancelled the operation.
	}

	DIB sourceDib, *destDib;
	std::ifstream file;
	LPBITMAPINFO format;
	SIZE size;

	file.open( fileName, std::ios::binary );
	if( sourceDib.readBitmap( file ) == false )
	{
		MessageBox( NULL, L"Could not read the image.", L"Message",
			MB_ICONEXCLAMATION | MB_OK );
		return;
	}
	destDib = new DIB();
	format = new BITMAPINFO();
	size = sourceDib.size();

	//Specify the format to which we will convert the new image.
	format->bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
	format->bmiHeader.biWidth = size.cx;
	format->bmiHeader.biHeight = size.cy;
	format->bmiHeader.biPlanes = 1;
	format->bmiHeader.biBitCount = 24;
	format->bmiHeader.biCompression = BI_RGB;

	// Make sure the height is positive, so we get a bottom-up DIB.
	if( format->bmiHeader.biHeight < 0 )
	{
		format->bmiHeader.biHeight *= -1;
	}
	if( sourceDib.convertTo( *destDib, format ) == false )
	{
		delete destDib;
		MessageBox( NULL, L"The image could not be converted to the correct format.", L"Message",
			MB_ICONEXCLAMATION | MB_OK );
		return;
	}
	task->_originalImage = destDib;

	//Call a function for operation-specific setup.
	bool setupComplete = false;
	switch( id )
	{
	case ID_MATHMATICAL_ADD:
		setupComplete = addSetup( task );
		break;
	case ID_MATHMATICAL_MULTIPLY:
		setupComplete = multiplySetup( task );
		break;
	case ID_MATHMATICAL_SUBTRACT:
		setupComplete = subtractSetup( task );
		break;
	default:
		break;
	}
	if( setupComplete == false )
	{
		delete task;
		return;
	}

	//The setup is complete. Start the filter operation, which will compute all new
	//pixel values.
	if( performOperation( task, id ) )
	{
		taskCompleted( task );
	}
	else
	{
		taskFailed( task );
	}
}

HWND MainWindow::hWnd() const
{
	return _hWnd;
}

HINSTANCE MainWindow::hInstance() const
{
	return _hInstance;
}

void MainWindow::taskCompleted( Task *task )
{
	//An operation has succeeded.

	//Get the ImageTab instance, by searching for the old DIB.
	ImageTab *imageTab;
	if( task->_operationType == Task::FILE_OPERATION )
	{
		//In a file operation, the _destinationImage is (besides the
		//result) the original image.
		imageTab = getTabForDIB( task->_destinationImage );
	}
	else
	{
		imageTab = getTabForDIB( task->_originalImage );
	}
	
	switch( task->_operationType )
	{
	case Task::REBUILDING_OPERATION:
	case Task::SIZING_OPERATION:
		//Change the DIB in the ImageTab instance.
		for( unsigned int i = 0; i < _tabCtrl->tabCount(); ++i )
		{
			imageTab->setDib( task->_destinationImage );
		}
		_imageBox->setDib( task->_destinationImage );
	case Task::FILE_OPERATION:
		delete task->_originalImage;
		break;
	default:
		//For a dot operation, no DIB instance has to be deleted.
		break;
	};

	//Repaint the image, to make the changes visible. InvalidateRect() will
	//send a WM_PAINT message.
	InvalidateRect( _imageBox->hWnd(), NULL, FALSE );
	delete task;

	imageTab->setHasUnsavedChanges( true );
}

void MainWindow::taskFailed( Task *task )
{
	//An operation has failed.
	switch( task->_operationType ) {
	case Task::REBUILDING_OPERATION:
	case Task::SIZING_OPERATION:
		if( task->_destinationImage )
		{
			delete task->_destinationImage;
		}
		break;
	case Task::FILE_OPERATION:
		delete task->_originalImage;
		break;
	default:
		//For a dot operation, no DIB instance has to be deleted.
		break;
	};
	delete task;

	MessageBox( NULL, L"The operation failed.", L"Message",
		MB_ICONEXCLAMATION | MB_OK );
}

ImageTab* MainWindow::getTabForDIB( DIB *dib )
{
	//Do a linear search through all tabs.
	for( unsigned int i = 0; i < _tabCtrl->tabCount(); ++i )
	{
		TabControl::Tab *tab = _tabCtrl->getTab( (int) i );
		if( tab )
		{
			if( typeid( *tab ) == typeid( ImageTab ) )
			{
				//We found an image tab.
				if( ( (ImageTab*) tab )->dib() == dib )
				{
					//We found the correct DIB; return this tab.
					return (ImageTab*) tab;
				}
			}
		}
	}
	return NULL;
}
